// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package traffic

import (
	pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime"

	"log"
	"sync"
	"time"

	"golang.org/x/text/language"
)

type BlockingError struct {
	cause error
}

func (e BlockingError) Error() string {
	return e.cause.Error()
}

type Alerts struct {
	ByLine     map[string][]uint
	ByTrip     map[string][]uint
	ByLineType map[LineType][]uint
	ByStop     map[string][]uint
	ByAgency   map[string][]uint
	Alerts     []Alert
}

type SpecificAlert struct {
	Header      string
	Description string
	URL         string
	Cause       AlertCause
	Effect      AlertEffect
}

type Alert struct {
	TimeRanges   [][2]time.Time
	Headers      map[language.Tag]string
	Descriptions map[language.Tag]string
	URLs         map[language.Tag]string
	Cause        AlertCause
	Effect       AlertEffect
}

type AlertCause uint

const (
	CAUSE_UNKNOWN           AlertCause = 0
	CAUSE_OTHER             AlertCause = 1
	CAUSE_TECHNICAL_PROBLEM AlertCause = 2
	CAUSE_STRIKE            AlertCause = 3
	CAUSE_DEMONSTRATION     AlertCause = 4
	CAUSE_ACCIDENT          AlertCause = 5
	CAUSE_HOLIDAY           AlertCause = 6
	CAUSE_WEATHER           AlertCause = 7
	CAUSE_MAINTENANCE       AlertCause = 8
	CAUSE_CONSTRUCTION      AlertCause = 9
	CAUSE_POLICE_ACTIVITY   AlertCause = 10
	CAUSE_MEDICAL_EMERGENCY AlertCause = 11
)

func alertCauseOfGtfs(v *pb.Alert_Cause) AlertCause {
	switch v {
	case pb.Alert_UNKNOWN_CAUSE.Enum():
		return CAUSE_UNKNOWN
	case pb.Alert_OTHER_CAUSE.Enum():
		return CAUSE_OTHER
	case pb.Alert_TECHNICAL_PROBLEM.Enum():
		return CAUSE_TECHNICAL_PROBLEM
	case pb.Alert_STRIKE.Enum():
		return CAUSE_STRIKE
	case pb.Alert_DEMONSTRATION.Enum():
		return CAUSE_DEMONSTRATION
	case pb.Alert_ACCIDENT.Enum():
		return CAUSE_ACCIDENT
	case pb.Alert_HOLIDAY.Enum():
		return CAUSE_HOLIDAY
	case pb.Alert_WEATHER.Enum():
		return CAUSE_WEATHER
	case pb.Alert_MAINTENANCE.Enum():
		return CAUSE_MAINTENANCE
	case pb.Alert_CONSTRUCTION.Enum():
		return CAUSE_CONSTRUCTION
	case pb.Alert_POLICE_ACTIVITY.Enum():
		return CAUSE_POLICE_ACTIVITY
	case pb.Alert_MEDICAL_EMERGENCY.Enum():
		return CAUSE_MEDICAL_EMERGENCY
	default:
		return CAUSE_UNKNOWN
	}
}

type AlertEffect uint

const (
	EFFECT_UNKNOWN             AlertEffect = 0
	EFFECT_OTHER               AlertEffect = 1
	EFFECT_NO_SERVICE          AlertEffect = 2
	EFFECT_REDUCED_SERVICE     AlertEffect = 3
	EFFECT_SIGNIFICANT_DELAYS  AlertEffect = 4
	EFFECT_DETOUR              AlertEffect = 5
	EFFECT_ADDITIONAL_SERVICE  AlertEffect = 6
	EFFECT_MODIFIED_SERVICE    AlertEffect = 7
	EFFECT_STOP_MOVED          AlertEffect = 8
	EFFECT_NONE                AlertEffect = 9
	EFFECT_ACCESSIBILITY_ISSUE AlertEffect = 10
)

func alertEffectOfGtfs(v *pb.Alert_Effect) AlertEffect {
	switch v {
	case pb.Alert_UNKNOWN_EFFECT.Enum():
		return EFFECT_UNKNOWN
	case pb.Alert_OTHER_EFFECT.Enum():
		return EFFECT_OTHER
	case pb.Alert_NO_SERVICE.Enum():
		return EFFECT_NO_SERVICE
	case pb.Alert_REDUCED_SERVICE.Enum():
		return EFFECT_REDUCED_SERVICE
	case pb.Alert_SIGNIFICANT_DELAYS.Enum():
		return EFFECT_SIGNIFICANT_DELAYS
	case pb.Alert_DETOUR.Enum():
		return EFFECT_DETOUR
	case pb.Alert_ADDITIONAL_SERVICE.Enum():
		return EFFECT_ADDITIONAL_SERVICE
	case pb.Alert_MODIFIED_SERVICE.Enum():
		return EFFECT_MODIFIED_SERVICE
	case pb.Alert_STOP_MOVED.Enum():
		return EFFECT_STOP_MOVED
	case pb.Alert_NO_EFFECT.Enum():
		return EFFECT_NONE
	case pb.Alert_ACCESSIBILITY_ISSUE.Enum():
		return EFFECT_ACCESSIBILITY_ISSUE
	default:
		return EFFECT_UNKNOWN
	}
}

// ............ feedID     trip/stop
var updates map[string]map[string][]Update
var alerts map[string]Alerts
var vehicleStatuses map[string]map[string]VehicleStatus
var cacheMx sync.Mutex

func GetAlerts(stopID, stopCode string, tripOffset int, ctx Context, t *Traffic, languages []language.Tag) []SpecificAlert {
	feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
	if err != nil {
		log.Printf("while getting feedInfo: %v\n", err)
		feedInfo = FeedInfo{}
	}

	var function func(string, string, string, Context, *Traffic) ([]Alert, error)
	if feedInfo.Name != "" {
		if _, ok := feedInfo.RealtimeFeeds[ALERTS]; ok {
			function = getGtfsRealtimeAlerts
		} else if isLuaAlertsScript(ctx) {
			function = getLuaRealtimeAlerts
		} else {
			return []SpecificAlert{}
		}
	}

	tripID := ""
	if tripOffset > 0 {
		file, err := openTrips(ctx)
		if err != nil {
			log.Printf("while opening trips: %v\n", err)
			return []SpecificAlert{}
		}
		defer file.Close()
		trip, err := GetTripByOffset(file, uint(tripOffset), ctx)
		if err != nil {
			log.Printf("while getting trip: %v\n", err)
			return []SpecificAlert{}
		}
		tripID = trip.Id
	}

	if function != nil {
		alerts, err := function(stopID, stopCode, tripID, ctx, t)
		if err != nil {
			log.Printf("while getting alerts: %v\n", err)
			return []SpecificAlert{}
		}
		return selectSpecificAlerts(alerts, languages)
	}
	return []SpecificAlert{}
}

func getVehiclePositions(ctx Context, t *Traffic, lb, rt Position) []VehicleStatus {
	feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
	if err != nil {
		log.Printf("while getting feedInfo: %v\n", err)
		feedInfo = FeedInfo{}
	}

	var function func(Context, Position, Position) ([]VehicleStatus, error)
	if feedInfo.Name != "" {
		if _, ok := feedInfo.RealtimeFeeds[VEHICLE_POSITIONS]; ok {
			function = getGtfsRealtimeVehicles
		} else if isLuaVehiclesScript(ctx) {
			function = getLuaRealtimeVehicles
		}
	}
	if function != nil {
		statuses, err := function(ctx, lb, rt)
		if err != nil {
			log.Printf("while getting vehicle positions: %v\n", err)
			return []VehicleStatus{}
		}
		file, err := openTrips(ctx)
		if err != nil {
			log.Printf("while opening trips file: %v", err)
			return []VehicleStatus{}
		}
		defer file.Close()
		statusesWithLine := make([]VehicleStatus, len(statuses))
		tripIDs := []string{}
		for i, status := range statuses {
			if status.LineID != "" && status.Headsign != "" {
				statusesWithLine[i] = status
			} else {
				tripIDs = append(tripIDs, status.TripID)
			}
		}
		trips, err := GetTrips(tripIDs, ctx, t)
		if err != nil {
			log.Printf("while getting trips file: %v", err)
			return []VehicleStatus{}
		}
		for i, status := range statuses {
			if status.LineID == "" || status.Headsign == "" {
				trip := trips[status.TripID]
				status.LineID = trip.LineID
				status.Headsign = trip.Headsign
				statusesWithLine[i] = status
			}
		}
		return statusesWithLine
	}
	return []VehicleStatus{}
}
